/*
#include <ota.h>
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2022-05-12     刘奇辉       the first version
 */
#include "rtthread.h"
#include "md5.h"
#include "pthread.h"
#include "stdio.h"
#include "ota.h"
#include "netupdata.h"
#include "ota.h"

const uint32_t Release=10004;             //当前代码版本号  0.1.1-> 0*10000+1*100+1=000101=101;
uint8_t otarunFlag=0;
struct rt_semaphore otaSem;

rt_thread_t otaThread=NULL;     //存储OTA线程号用于终止和启动
uint8_t onRecvDataFlag = 0;     //获取数据后为1，将获取的数据解析后为0
uint8_t otaCommonPayloadBuf[DOWNLOAD_DATA_BLOCK_SIZE] = { 0 };  //存放解析后的纯升级包部分
struct typeofDownloadInfo  DownInfo={0};
struct OTAMsgBuf msgBuf = { 0 };
struct typeofUpdateInfo  OtaInfo={0};

typeofDownloadInfo_t ota_get_info_struct()
{
    return &DownInfo;
}
uint32_t get_Version()
{
    return Release;
}

rt_uint8_t tcpConnect(int socket)
{
    return Air724x_tcp_establishConn(socket, DownInfo.ota_url, DownInfo.ota_port);
}

uint32_t otaVersinToint(otaversion_t* version)
{
    return (version->ota_MajorVersion * 10000 + version->ota_ChildVersion * 100 + version->ota_ReVision);

}

otaversion_t otaVersinToStuct(uint32_t version)
{
    otaversion_t vsionstr;
    vsionstr.ota_MajorVersion=version/10000%100;
    vsionstr.ota_ChildVersion=version/100%100;
    vsionstr.ota_ReVision=version%100;

    return vsionstr;
}
void ota_notify()
{
    rt_sem_release(&otaSem);
}

void write_Flash_info(struct typeofUpdateInfo* _OtaInfo,uint32_t _len){
    FLASH_EraseInitTypeDef pEraseInit;
    uint32_t PageError;
    HAL_FLASH_Unlock();
    pEraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
    pEraseInit.PageAddress = FLASH_ADDR_PARAMETERS;
    pEraseInit.NbPages = _len%FLASH_PAGE_SIZE!=0?_len/FLASH_PAGE_SIZE+1:_len/FLASH_PAGE_SIZE;
    if (HAL_FLASHEx_Erase(&pEraseInit, &PageError) != HAL_OK){
        rt_kprintf("Erase Parameter To Flash Fail!!!\n");
    }
    HAL_FLASH_Lock();
    //因为FLASH_ADDR_PARAMETERS位置只存储版本信息，所以未先读取而整页的擦除不影响程序的运行
    if(WriteApplicationDataToFlash(FLASH_ADDR_PARAMETERS,(uint8_t*)_OtaInfo,_len)==0){
        rt_kprintf("Write Parameter To Flash success!!!\n");
    }
}
uint8_t otaStatusGet()
{
    return otarunFlag;
}
void otaModeThreadStop()
{
    if(otaThread!=NULL)rt_thread_delete(otaThread);
    rt_kprintf("ota thread stop!!!\n");
}

int otaModuleInit()
{
    OtaInfo.runRelease=Release;
    rt_sem_init(&otaSem, "otasem", 0,RT_IPC_FLAG_PRIO);

    write_Flash_info(&OtaInfo,sizeof(struct typeofUpdateInfo));

    otaThread=rt_thread_create("ota", otaThreadEntry, NULL, 4096, 21, 5);  //名字，入口，参数，堆大小,优先级，时间片(数目)
    rt_thread_startup(otaThread);
    rt_kprintf("[OTA]ota thread startup!!!!\n");
    return 0;
}

//1.进入里升级模式
//2.判断当前的升级状态,升级完成需要上报设备升级是否完成
//3.上报成功或者失败进入到 正常运行模式
void otaThreadEntry(void *para)
{
#define TCPCONTRYNUM 5
    uint8_t socket = 0;                                 //默认socketid
    while (1)
    {
        otarunFlag=0;
        if (rt_sem_take(&otaSem, RT_WAITING_FOREVER) == RT_EOK&&DownInfo.ota_VersionNumber>Release)
        {
            otarunFlag=1;
            updata_cmdReback(0,DownInfo.id,2);
            updata_otaStaStruct_Set(1);

            rt_thread_mdelay(1000);
            rt_kprintf("[OTA]Update Version from %d to %d\r\n",Release,DownInfo.ota_VersionNumber);
            while(tcpConnect(socket))
            {
                rt_kprintf("[OTA]tcp connect error\n");
                Air724x_tcp_resetTcpConn(socket);
                rt_thread_mdelay(1000);
            }
            rt_kprintf("[OTA]tcp connect success\n");
            if (EraseDownloadSpace() != 0)   //先擦除才能写
            {
                updata_otaStaStruct_Set(-1);
                rt_kprintf("[OTA] Erase Download Flash failed\r\n");
                continue;
            }else{
                updata_otaStaStruct_Set(2);
                rt_thread_mdelay(1000);

                rt_kprintf("[OTA] Erase Download Flash success\r\n");
            }
            if (RT_EOK == otaDownloadFirmware(&DownInfo, socket, DownInfo.ota_size, 10000, 10))
            {
                rt_kprintf("[OTA] download ota code success!!!\r\n");
                OtaInfo.flag=1;
                OtaInfo.downloadRelease=DownInfo.ota_VersionNumber;
                OtaInfo.downloadSize=DownInfo.ota_size;
                write_Flash_info(&OtaInfo,sizeof(struct typeofUpdateInfo));
                //向服务器发送升级成功********未实现*****
                updata_otaStaStruct_Set(3);
                rt_thread_mdelay(10000);
                rt_kprintf("reboot!!!\r\n");
                __set_FAULTMASK(1);
                HAL_NVIC_SystemReset();
            }else{
                updata_otaStaStruct_Set(-1);
                rt_kprintf("[OTA] download ota code fail!!!\r\n");
            }
        }else{
            rt_kprintf("[OTA]Run Version: %d\r\n",Release);
            rt_kprintf("[OTA]Ota Version: %d\r\n",DownInfo.ota_VersionNumber);
            rt_kprintf("[OTA]No need to update!!!\r\n");
            //向服务器发送升级成功********未实现*****
            updata_cmdReback(1,DownInfo.id,2);

        }
    }
}

//擦除下载空间，因为Flash需要先擦除，才能写，所以该函数是下一个函数正常执行的关键
/*
    因为Flash的编程原理都是只能将1写为0，而不能将0写为1，所以在进行Flash编程之前，必须将对应的块擦除，
    而擦除的过程就是把所有位都写为1的过程，块内的所有字节变为0xFF。
*/
int EraseDownloadSpace(void)
{
    int ret = 0;
    uint32_t PageError;
    FLASH_EraseInitTypeDef pEraseInit;

    HAL_FLASH_Unlock();
    pEraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
    pEraseInit.PageAddress = FLASH_ADDR_DOWNLOAD_DATA;
    pEraseInit.NbPages = DOWNLOAD_FLASH_SIZE%FLASH_PAGE_SIZE==0?DOWNLOAD_FLASH_SIZE/FLASH_PAGE_SIZE:DOWNLOAD_FLASH_SIZE/FLASH_PAGE_SIZE+1;
    if (HAL_FLASHEx_Erase(&pEraseInit, &PageError) != HAL_OK)
        ret = 1;
    HAL_FLASH_Lock();
    return ret;
}

//正常是先擦除才能写但是由于提前将整个下载区域擦除，所以不需要写入特定页是先擦除该页
uint8_t WriteApplicationDataToFlash(uint32_t Address,uint8_t* data, uint32_t len)  //确保len%4==0
{
    int j = 0;
    int ret = 0;
    uint32_t* _data=(uint32_t*)data;
    HAL_StatusTypeDef FLASHStatus;
    if(len%4!=0){
        int addtail=len%4;
        for(int i=0;i<addtail;i++){
            data[len]=0xFF;
            len++;
        }
    }
    HAL_FLASH_Unlock();
    for(j=0;j<len/4;j++){
        FLASHStatus = HAL_FLASH_Program(TYPEPROGRAM_WORD, Address, _data[j]);
        Address += 4;
        if (FLASHStatus != HAL_OK)
        {
            rt_kprintf("写入Flash错误,错误代码HAL_StatusTypeDef:%d！！！！\n",FLASHStatus);
            ret=1;   //失败
            break;
        }
    }
    HAL_FLASH_Lock();
    return ret;
}


//解析收到的数据，提取有效数据
rt_err_t unpacketFrameData(const rt_uint8_t *frameData, rt_uint16_t frameDataLength, rt_uint8_t *payload,
        rt_uint16_t payloadCapacity, rt_uint16_t *payloadLen)
{
    int dataLen = -1;
    const char *pos = NULL;
    pos = rt_strstr((char *) frameData, "Content-Length:"); //查找"Content-Length:"在frameData出现的位置，char*
    if (pos)
    {
        sscanf(pos, "Content-Length: %d", &dataLen);        //将pos字符串中的数据长度提取到dataLen中
    }
    else
    {
        return -1;
    }
    if (dataLen > payloadCapacity)
        return -1;
    *payloadLen = dataLen;
    pos = rt_strstr((char *) frameData, "Allow-Methods: *");

    if (pos)
    {
        //将payload的DOWNLOAD_DATA_BLOCK_SIZE个字节填为0
        rt_memset(payload, 0, DOWNLOAD_DATA_BLOCK_SIZE);
        //pos + rt_strlen("Allow-Methods: *\r\n\r\n")  == pos+20
        rt_memcpy(payload, pos + rt_strlen("Allow-Methods: *\r\n\r\n"), dataLen);
    }
    else
    {
        return -1;
    }
    return 0;
}

const char DecToHex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

void byteToHexString(uint8_t byte, char outStr[2])
{
    outStr[0] = DecToHex[byte >> 4];
    outStr[1] = DecToHex[byte & 0x0F];
}

void bytesToHexString(const uint8_t *data, uint16_t dataSize, char *hexString)
{
    for (uint16_t i = 0; i < dataSize; ++i)
    {
        byteToHexString(data[i], hexString + i * 2);
    }
}
//将要发送给服务器的请求信息封装成帧
int ota_Data_Request(char url[],char port[], char FilePath[], uint8_t socket, uint32_t start_len, uint32_t end_len)
{
    static char ota_send_buff_ASCII[512];
    int ota_send_len;
    //1.构建请求格式
    rt_memset(ota_send_buff_ASCII, 0, 512);

    rt_sprintf(ota_send_buff_ASCII,"GET %s HTTP/1.1\r\nHost: %s:%s\r\nRange: bytes=%d-%d\r\nAccept: */*\r\nUser-Agent: GeneralDownloadApplication\r\n\r\n",FilePath, url,port, start_len, end_len);
    //rt_kprintf("%s", ota_send_buff_ASCII);
    ota_send_len = rt_strlen(ota_send_buff_ASCII);


    if (RT_EOK == Air724x_tcp_sendData(socket, ota_send_buff_ASCII,ota_send_len ))
        return ota_send_len;
    else
    {
        return 1;
    }

}

//***********************收到服务器升级包数据的第一步
void onRevOTAFrame(uint8_t *frameData, rt_size_t frameSize)
{
    rt_memset(msgBuf.buf, 0, MSG_REV_BUF_SIZE);
    rt_memcpy(msgBuf.buf, frameData, frameSize);
    msgBuf.msgLen = frameSize;
    if ((onRecvDataFlag) == 0)
    {
        onRecvDataFlag = 1;
        rt_kprintf("[OTA]Recv Data (%d)\n", frameSize);
    }
}

//***********************收到服务器升级包数据的第二步
//从数据buff中读取有用数据，payloadCapacity=sizeof(payload)
static int recvOTAData(uint8_t *payload, uint16_t payloadCapacity, uint32_t timeoutMs)
{
    if (RT_EOK == wait_flag(&onRecvDataFlag, 100, 50))
    {
        onRecvDataFlag = 0;
        int dataLen = 0;
        const char *pos = NULL;
        pos = rt_strstr((char *) msgBuf.buf, "Content-Length:"); //内容的长度(字节)
        if (pos)
        {
            sscanf(pos, "Content-Length: %d", &dataLen);
        }
        else
        {
            return 0;
        }
        if (dataLen > payloadCapacity)
            return 0;
        pos = rt_strstr((char *) msgBuf.buf, "Accept-Ranges: bytes");
        if (pos)
        {
            rt_memset(payload, 0, DOWNLOAD_DATA_BLOCK_SIZE);
            rt_memcpy(payload, pos + rt_strlen("Accept-Ranges: bytes\r\n\r\n"), dataLen);
            return dataLen;
        }
        else
        {
            return 0;
        }
    }
    return 0;

}

//向服务器请求升级的第一步
uint8_t otaDownloadFirmware(struct typeofDownloadInfo* otaInfo, uint8_t socket, uint32_t firmwareSize, uint32_t timeoutMs,
        const uint16_t retryNum)
{
    uint16_t frameNo = 1;           // 帧序号
    uint32_t offset = 0;            // 偏移
    uint16_t curRetryNum = 0;       // 请求获取不到数据次数
    uint8_t tcpConTryNum = 0;       // tcp重连次数
    uint16_t blockSize = DOWNLOAD_DATA_BLOCK_SIZE;      //块大小
    int payloadLen = 0;                                 //有效数据长度

    uint8_t downloadFinishFlag = 0;
    md5_context ctx;   //存储需要获取MD5校验码的字符串
    md5_starts(&ctx);
    uint8_t downloadDataMD5[16] = { 0 };
    uint8_t downloadDataMD5HexString[32] = { 0 };
    // retryNum最多尝试多少次，firmwareSize总共的大小
    while (offset < firmwareSize && (retryNum == 0 || timeoutMs == 0 || curRetryNum < retryNum))
    {
        tcpConTryNum = 0;
        if (0 == Air724x_tcp_status(socket)) //每次下载检测连接状态
        {
            Air724x_tcp_resetTcpConn(0);

            while (tcpConTryNum < retryNum)
            {
                if (RT_EOK == Air724x_tcp_establishConn(socket, otaInfo->ota_url, otaInfo->ota_port))   //注意端口号是否依旧为80
                    break;
                tcpConTryNum++;
                rt_thread_mdelay(1000);
            }
        }
        if (tcpConTryNum >= retryNum)
        {
            return RT_ETIMEOUT;
        }
        blockSize = firmwareSize - offset < DOWNLOAD_DATA_BLOCK_SIZE ? firmwareSize - offset : DOWNLOAD_DATA_BLOCK_SIZE;
        rt_kprintf("[OTA] Download frameNo.%d offset:%d FWsize:%d %d%% block size:%d retryNum:%d/%d...\n", frameNo,
                offset, firmwareSize, (int) (((float) offset / (float) firmwareSize) * 100.0f), blockSize, curRetryNum,
                retryNum);
        //otaInfo->ota_route   文件在Http服务器存放的目录
        if (ota_Data_Request(otaInfo->ota_url, otaInfo->ota_port, otaInfo->ota_route, socket, offset,
                offset + blockSize)==1)
        {
            rt_kprintf("send error\n");
            continue; // 重试本次下载
        }

        int packLen = recvOTAData(otaCommonPayloadBuf, sizeof(otaCommonPayloadBuf), timeoutMs);
        if (packLen == 0)
        { // 没收到数据
            ++curRetryNum;
            onRecvDataFlag=0;
            rt_kprintf("[OTA] Wait resp timeout!\n");
            Air724x_tcp_resetTcpConn(socket);
            continue; // 重试本次下载
        }
        curRetryNum = 0;
        rt_kprintf("[OTA] Got some ota data,");
        payloadLen = packLen;

        if (payloadLen > 0)
        {
            rt_kprintf("gotPacketDataSize:%d\n", payloadLen);

            md5_update(&ctx, otaCommonPayloadBuf, payloadLen);
            if (RT_EOK != WriteApplicationDataToFlash(FLASH_ADDR_DOWNLOAD_DATA+offset, otaCommonPayloadBuf, payloadLen))
                return 1; //写数据到flash
            frameNo += 1;
            offset += payloadLen; // offset偏移，获取下一段数据
        }
        else
        {
            rt_kprintf("FrameNo.%d error\n", frameNo);
        }
    }
    downloadFinishFlag = 0;
    if (offset >= firmwareSize)
    {
        if (offset > firmwareSize)
            rt_kprintf("[OTA] Warning! offset > firmwareSize!!!\n");
        rt_kprintf("[OTA] Download finished!\n");
        downloadFinishFlag = 1;
    }
    else
    {
        if (retryNum != 0 && timeoutMs != 0 && curRetryNum >= retryNum)
        {
            rt_kprintf("[OTA] Download Timeout!\n");
            return RT_ETIMEOUT;
        }
        else
        {
            rt_kprintf("[OTA] Unknow Error ! offset:%lu fmsize:%lu retry:%lu timeout:%lu curRetry:%lu\n", offset,
                    firmwareSize, retryNum, timeoutMs, curRetryNum);
        }
    }
    if (downloadFinishFlag)
    {
        // 下载完成，接下来进行MD5检验
        md5_finish(&ctx, downloadDataMD5);
        bytesToHexString(downloadDataMD5, 16, (char*) downloadDataMD5HexString);
        if (0 != rt_memcmp(otaInfo->ota_crc, downloadDataMD5HexString, 32))
        {
            rt_kprintf("[OTA] md5 check error!\n");
            return RT_ERROR;
        }
        else
        {
            rt_kprintf("[OTA] md5 check successed!\n");
        }
    }

    return RT_EOK;
}

INIT_APP_EXPORT(otaModuleInit);

